package xyz.xmh.dao.file.impl;

import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufMono;
import xyz.xmh.api.FileEnum;
import xyz.xmh.dao.file.ReactiveFileDao;
import xyz.xmh.io.DownloadResource;
import xyz.xmh.pojo.enums.CategoryEnum;
import xyz.xmh.pojo.enums.CheckNameEnum;
import xyz.xmh.pojo.request.FileIdRequest;
import xyz.xmh.pojo.request.file.*;
import xyz.xmh.pojo.response.file.CreateFileResponse;
import xyz.xmh.pojo.response.file.DownloadUrlResponse;
import xyz.xmh.util.HashUtils;
import xyz.xmh.util.ProofV1Utils;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;

/**
 * @author xuMingHai
 */
public class ReactiveFileDaoImpl implements ReactiveFileDao {

    private final WebClient webClient;

    public ReactiveFileDaoImpl(WebClient webClient) {
        this.webClient = webClient;
    }

    @Override
    public WebClient.ResponseSpec list() {
        return list(new ListRequest());
    }

    @Override
    public WebClient.ResponseSpec list(ListRequest listRequest) {
        final FileEnum list = FileEnum.LIST;
        return webClient.method(list.getHttpMethod()).uri(list.getApi())
                .bodyValue(listRequest)
                .retrieve();
    }

    @Override
    public WebClient.ResponseSpec search(String fileName) {
        final SearchRequest searchRequest = new SearchRequest();
        searchRequest.setQuery(fileName);
        final FileEnum search = FileEnum.SEARCH;
        return webClient.method(search.getHttpMethod()).uri(search.getApi())
                .bodyValue(searchRequest)
                .retrieve();
    }

    @Override
    public WebClient.ResponseSpec search(String fileName, CategoryEnum categoryEnum) {
        final SearchRequest searchRequest = new SearchRequest();
        searchRequest.setQuery(fileName, categoryEnum);
        final FileEnum search = FileEnum.SEARCH;
        return webClient.method(search.getHttpMethod()).uri(search.getApi())
                .bodyValue(searchRequest)
                .retrieve();
    }

    @Override
    public WebClient.ResponseSpec search(SearchRequest searchRequest) {
        final FileEnum search = FileEnum.SEARCH;
        return webClient.method(search.getHttpMethod()).uri(search.getApi())
                .bodyValue(searchRequest)
                .retrieve();
    }

    @Override
    public WebClient.ResponseSpec get(String fileId) {
        final FileIdRequest fileIdRequest = new FileIdRequest(fileId);
        final FileEnum get = FileEnum.GET;
        return webClient.method(get.getHttpMethod()).uri(get.getApi())
                .bodyValue(fileIdRequest)
                .retrieve();
    }

    @Override
    public WebClient.ResponseSpec getDownloadUrl(String fileId) {
        final FileIdRequest fileIdRequest = new FileIdRequest(fileId);
        final FileEnum getDownloadUrl = FileEnum.GET_DOWNLOAD_URL;
        return webClient.method(getDownloadUrl.getHttpMethod()).uri(getDownloadUrl.getApi())
                .bodyValue(fileIdRequest)
                .retrieve();
    }

    @Override
    public Mono<ResponseEntity<Resource>> downloadFile(String fileId, HttpHeaders requestHttpHeaders) {
        return getDownloadUrl(fileId).bodyToMono(DownloadUrlResponse.class)
                .mapNotNull(downloadUrlResponse -> {
                    try {
                        return DownloadResource.createResource(downloadUrlResponse, requestHttpHeaders);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return null;
                }).map(downloadResource -> new ResponseEntity<>(downloadResource, downloadResource.getResponseHttpHeaders(), downloadResource.getHttpStatus()));
    }

    @Override
    public WebClient.ResponseSpec getMultiDownloadUrl(MultiDownloadUrlRequest multiDownloadUrlRequest) {
        final FileEnum multiDownloadUrl = FileEnum.MULTI_DOWNLOAD_URL;
        return webClient.method(multiDownloadUrl.getHttpMethod()).uri(multiDownloadUrl.getApi())
                .bodyValue(multiDownloadUrlRequest)
                .retrieve();
    }

    @Override
    public Mono<ResponseEntity<Resource>> multiDownloadFile(MultiDownloadUrlRequest multiDownloadUrlRequest, HttpHeaders httpHeaders) {
        // TODO: 多文件下载不能使用
        return null;
    }

    @Override
    public WebClient.ResponseSpec createFolder(String parentFileId, String name, CheckNameEnum checkNameEnum) {
        final CreateFolderRequest createFolderRequest = new CreateFolderRequest();
        createFolderRequest.setParentFileId(parentFileId);
        createFolderRequest.setName(name);
        createFolderRequest.setCheckNameMode(checkNameEnum);
        final FileEnum createWithFolders = FileEnum.CREATE_WITH_FOLDERS;
        return webClient.method(createWithFolders.getHttpMethod()).uri(createWithFolders.getApi())
                .bodyValue(createFolderRequest)
                .retrieve();
    }

    @Override
    public WebClient.ResponseSpec createFolder(String parentFileId, String name) {
        return createFolder(parentFileId, name, CheckNameEnum.AUTO_RENAME);
    }

    @Override
    public WebClient.ResponseSpec createFolder(String name) {
        return createFolder("root", name);
    }

    @Override
    public Mono<CreateFileResponse> uploadFile(String parentFileId, Path path, CheckNameEnum checkNameEnum) {
        // 初始化请求参数
        final CreateFileRequest createFileRequest = new CreateFileRequest();
        createFileRequest.setParentFileId(parentFileId);
        createFileRequest.setCheckNameMode(checkNameEnum);
        createFileRequest.setName(path.getFileName().toString());
        createFileRequest.setSize(path.toFile().length());

        // 文件hash值
        if (createFileRequest.getSize() > 0) {
            createFileRequest.setContentHash(HashUtils.sha1(path));
            // 文件大于10M拆分
            int scale = 1024 * 1024 * 10;
            if (createFileRequest.getSize() > scale) {
                long size = createFileRequest.getSize();
                final List<CreateFileRequest.PartInfo> list = new ArrayList<>();
                int i;
                for (i = 1; size > scale; i++) {
                    list.add(new CreateFileRequest.PartInfo(i));
                    size -= scale;
                }

                if (size != 0) {
                    list.add(new CreateFileRequest.PartInfo(++i));
                }

                createFileRequest.setPartInfoList(list);

            }

        }

        // 随机的一段文件字节
        createFileRequest.setProofCode(ProofV1Utils.getCode(path));

        return upload(createFileRequest, 0).flatMap(createFileResponse -> {
            // 判断是否是快传
            if (!createFileResponse.isRapidUpload()) {
                final List<CreateFileResponse.PartInfo> partInfoList = createFileResponse.getPartInfoList();
                for (CreateFileResponse.PartInfo partInfo : partInfoList) {

                }
            }
            return Mono.just(createFileResponse);
        });

    }

    private Mono<CreateFileResponse> upload(CreateFileRequest createFileRequest, int i) {
        // 发送请求
        final FileEnum createWithFolders = FileEnum.CREATE_WITH_FOLDERS;
        final int frequency = i;
        return webClient.method(createWithFolders.getHttpMethod()).uri(createWithFolders.getApi())
                .bodyValue(createFileRequest)
                .retrieve()
                .onStatus(httpStatus -> httpStatus.value() == 409, clientResponse -> {
                    // 阿里云盘的文件上传有时候会出现409，多重复上传几次
                    if (frequency < 5) {
                        upload(createFileRequest, frequency + 1);
                    }
                    return clientResponse.createException();
                })
                .bodyToMono(CreateFileResponse.class);

    }


    @Override
    public Mono<CreateFileResponse> uploadFile(String parentFileId, Path path) {
        return uploadFile(parentFileId, path, CheckNameEnum.AUTO_RENAME);
    }

    @Override
    public Mono<CreateFileResponse> uploadFile(Path path) {
        return uploadFile("root", path);
    }

    @Override
    public WebClient.ResponseSpec update(UpdateRequest updateRequest) {
        final FileEnum update = FileEnum.UPDATE;
        return webClient.method(update.getHttpMethod()).uri(update.getApi())
                .bodyValue(updateRequest)
                .retrieve();
    }

    @Override
    public WebClient.ResponseSpec update(String fileId, String name) {
        final UpdateRequest updateRequest = new UpdateRequest(fileId, name);
        return update(updateRequest);
    }
}
